Skip to content

Host client-streaming and bidirectional gRPC methods - #1641

Merged
jlouvel merged 3 commits into
3.0from
feat/grpc-server-bidi-streaming
Nov 5, 2026
Merged

jlouvel merged 3 commits into
3.0from
feat/grpc-server-bidi-streaming

Conversation

@jlouvel

@jlouvel jlouvel commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

First step of R6a (#1561): GrpcServerAdapter hosts client-streaming and bidirectional methods, so services of every streaming type now run on Restlet. Streaming in GrpcClientAdapter follows in a separate PR.

Change

RestletServerHost read the whole request (at most one message) before handing it to the SDK, so start() rejected client-streaming and bidirectional methods. Now:

  • The request is read on its own virtual thread and handed to the SDK as it arrives. The handler returns once the response head is ready, as before, so responses stream out while the client is still sending. The Netty server keeps an HTTP/2 request body readable until the response completes, so a bidirectional call exchanges messages both ways over one stream.
  • Inbound flow control.
    • Request bytes handed to the SDK but not yet consumed by its deframer stay within setMaxQueuedBytes.
    • The deframer only reads the messages the application requested, and reports consumption through bytesRead.
    • So a slow or paused consumer stops the reads, and HTTP/2 flow control then stops the client.
    • A single message larger than the budget still streams through, since the deframer consumes it as it arrives.
  • Limits. Message size stays bounded by the SDK deframer (setMaxMessageBytes, RESOURCE_EXHAUSTED). setMaxQueuedBytes now documents that it applies to each direction.
  • Errors.
    • A request stream that breaks while being read ends the call as CANCELLED, as grpc-netty reports a peer reset; it was INVALID_ARGUMENT.
    • A read still stuck after the call ended is reset after 200 ms, as before. The handler no longer waits for that reset, since it isn't the thread reading.
  • start() now only rejects methods of type UNKNOWN.

changes.md, readme.md and the adapter Javadoc are updated.

Tests

GrpcServerAdapterTestCase uses grpc-java's own Netty client over h2c. The new tests are:

Test Checks
clientStreamingReceivesEveryMessage 200 × 4 KiB messages, one summary reply
bidirectionalCallsExchangeMessagesBothWays Each reply is received before the next request is sent, 5 rounds on one stream
unrequestedMessagesPushBackOnTheClient With a 32 KiB budget and the server requesting nothing, a client that respects readiness has sent under half of 4 MiB after 1 s. Once requested, all 1024 messages arrive
messagesLargerThanTheQueueBudgetStreamThrough A 512 KiB unary request through a 16 KiB budget
cancellingAStreamingCallReleasesItsSlot Client cancel mid-stream reaches onCancel and frees the only slot
unknownMethodTypesFailExplicitly Replaces the old "streaming rejected" test

Two existing tests were adjusted for the reader thread:

  • stalledRequestBodyIsResetAtTheDeadline waits for the reset rather than asserting it before the handler returns.
  • unreadableRequestStillSendsItsGrpcStatus now expects CANCELLED.

mvn -o verify -pl org.restlet.ext.grpc passes: 75 tests. The 74 tests before the last one passed three runs in a row.

Not in this PR

  • Streaming in GrpcClientAdapter, which is still unary-only.
  • Idle-disconnect and long-lived stream tests. They come with the client side, which can drive both ends.
  • Connection controls (GOAWAY/draining, PING/keepalive), which stay rejected.

🤖 Generated with Claude Code

The server host read the whole request before handing it to the SDK, so
GrpcServerAdapter rejected client-streaming and bidirectional methods.
Read the request on its own virtual thread instead, handing it to the
SDK as it arrives: the handler returns once the response head is ready,
so responses stream out while the client still sends, and a
bidirectional call exchanges messages both ways over one HTTP/2 stream.

Request bytes handed to the SDK but not yet consumed by its deframer,
which only reads the messages the application requested, stay within
the queue budget, so an unread request pushes back on the client.
Message size stays bounded by the SDK's deframer. A request stream that
breaks while read now ends the call as CANCELLED, like a peer reset in
grpc-netty, and a read stuck after the call ended is still reset.

Part of #1561 (R6a).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Jerome Louvel <[email protected]>
@jlouvel
jlouvel requested a balanced review from Copilot November 5, 2026 03:20
@jlouvel jlouvel self-assigned this Nov 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical concurrency bugs can leave response writes blocked beyond deadlines and misinterpret local request-body closure as a failure, cancelling bidirectional calls and releasing admission before delivery completes.

2 open issues
What changed in this PR

Extends experimental gRPC server hosting to client-streaming and bidirectional methods, advancing #1561 while leaving the client adapter unary-only.

Changes:

  • Reads requests on a separate virtual thread with demand-driven inbound queue limits.
  • Allows all known method types and updates streaming support documentation.
  • Adds interoperability tests for streaming, backpressure, large messages, and cancellation.
File Description
readme.md Updates supported gRPC modes and remaining client work.
org.restlet.ext.grpc/​src/​test/​java/​org/​restlet/​ext/​grpc/​GrpcServerAdapterTestCase.java Adds streaming tests and adjusts read-error and reset expectations.
org.restlet.ext.grpc/​src/​main/​java/​org/​restlet/​ext/​grpc/​internal/​RestletServerHost.java Adds asynchronous request reading and inbound byte accounting.
org.restlet.ext.grpc/​src/​main/​java/​org/​restlet/​ext/​grpc/​GrpcServerAdapter.java Accepts streaming methods and documents per-direction queue limits.
changes.md Records expanded server streaming support and flow control.

Review effort: Balanced

jlouvel and others added 2 commits November 5, 2026 06:07
…lose

The request reader and the response writer now run concurrently but
shared one in-progress marker, so a read ending while a response write
was stuck cleared the writer's marker, and a later deadline or shutdown
no longer reset that write. Track reads and writes separately, and
reset the stream if either stays stuck after the call ended.

When a bidirectional service finishes before the client half-closes,
the Netty server closes the unread request body after the terminal
write, and the reader took that for a peer failure, cancelling a
completed call before its delivery. Ignore read failures once the
response ended; the delivery and disconnect signals settle the call.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Jerome Louvel <[email protected]>
The request reader caught InterruptedException without restoring the
thread's interrupt status (SonarCloud S2142). Restore it before
cancelling the call, and do the same in a test helper. Comment the
listener methods the tests don't use, and use unnamed catch variables.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Jerome Louvel <[email protected]>
@sonarqubecloud

sonarqubecloud Bot commented Nov 5, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The review identifies no active issues with the expanded server-side gRPC streaming support.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

@jlouvel
jlouvel merged commit 0738961 into 3.0 Nov 5, 2026
4 checks passed
@jlouvel
jlouvel deleted the feat/grpc-server-bidi-streaming branch November 5, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants